package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.cache.CategoryCache;
import com.tianji.api.client.course.CategoryClient;
import com.tianji.api.dto.course.CategoryBasicDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.utils.CodeUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.common.constants.Constant.EXCHANGE_CODE;
import static com.tianji.promotion.enums.CouponStatus.*;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author WFan
 * @since 2025-04-03
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
    private final ICouponScopeService scopeService;
    private final IExchangeCodeService codeService;
    private final CategoryCache categoryCache;
    private final IUserCouponService userCouponService;




    /**
     * 新增优惠券
     *
     * @param couponFormDTO
     */
    @Override
    @Transactional
    public void createCoupon(CouponFormDTO couponFormDTO) {
        // 1.将优惠券信息保存到数据库中
        Coupon coupon = BeanUtil.toBean(couponFormDTO, Coupon.class);
        save(coupon);

        // 2.判断是否有限定范围
        if (!couponFormDTO.getSpecific()){
            return;
        }

        // 3.保存优惠券限定范围信息
        saveScope(couponFormDTO, coupon);
    }

    /**
     * 保存优惠券限定范围信息
     * @param couponFormDTO
     * @param coupon
     */
    private void saveScope(CouponFormDTO couponFormDTO, Coupon coupon) {
        List<Long> scopes = couponFormDTO.getScopes();
        if (ObjectUtil.isEmpty(scopes)){
            throw new BadRequestException("优惠券限定范围参数不能为空");
        }
        List<CouponScope> scopeList = scopes.stream().map(r -> {
            CouponScope couponScope = new CouponScope();
            couponScope.setCouponId(coupon.getId());
            couponScope.setBizId(r);
            return couponScope;
        }).collect(Collectors.toList());
        scopeService.saveBatch(scopeList);
    }

    /**
     * 分页查询优惠券列表
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<CouponPageVO> queryCouponPage(CouponQuery query) {
        // 1.分页查询优惠券列表
        Page<Coupon> page = this.lambdaQuery().eq(ObjectUtil.isNotEmpty(query.getType()), Coupon::getType, query.getType())
                .eq(ObjectUtil.isNotEmpty(query.getStatus()), Coupon::getStatus, query.getStatus())
                .like(StrUtil.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());

        List<Coupon> records = page.getRecords();
        if (ObjectUtil.isEmpty(records)){
            return PageDTO.empty(page);
        }

        List<CouponPageVO> voList = BeanUtil.copyToList(records, CouponPageVO.class);

        return PageDTO.of(page, voList);
    }

    /**
     * 发放优惠券
     *
     * @param id
     */
    @Override
    @Transactional
    public void issueCoupon(Long id, CouponIssueFormDTO issueFormDTO) {
        // 1.查询优惠券信息
        Coupon coupon = this.lambdaQuery().eq(Coupon::getId, id).one();
        if (ObjectUtil.isEmpty(coupon)){
            // 如果为空，抛出异常
            throw new BadRequestException("优惠券不存在");
        }
        // 判断优惠券状态，如果状态不是待发放或暂停，抛出异常
        if (ObjectUtil.notEqual(coupon.getStatus(), DRAFT) && ObjectUtil.notEqual(coupon.getStatus(), PAUSE)){
            throw new BadRequestException("优惠券状态不正确");
        }

        // 2.是否立即发放
        Boolean isIssueNow = (ObjectUtil.isEmpty(issueFormDTO.getIssueBeginTime()) || issueFormDTO.getIssueBeginTime().isBefore(LocalDateTime.now()));

        // 3.保存发放信息
        BeanUtil.copyProperties(issueFormDTO, coupon);
        updateById(isIssueNow ? coupon.setStatus(ISSUING) : coupon.setStatus(UN_ISSUE));

        // 4.立即发放则生成优惠券码,否则结束方法
        if (!isIssueNow){
            return;
        }

        // 判断是否需要生成优惠券码，优惠券类型必须是兑换码，优惠券状态必须是待发放
        if (ObjectUtil.equals(coupon.getObtainWay(), ObtainType.ISSUE) ){
            codeService.asyncGenerateCode(coupon);
        }
    }

    /**
     * 修改优惠券
     *
     * @param id
     * @param couponFormDTO
     */
    @Override
    @Transactional
    public void updateCoupon(Long id, CouponFormDTO couponFormDTO) {
        // 1.查询优惠券信息
        Coupon coupon = this.lambdaQuery().eq(Coupon::getId, id).one();
        if (ObjectUtil.isEmpty(coupon)){
            throw new BadRequestException("优惠券不存在");
        }

        // 2.优惠券是否待发放或未开始
        if (ObjectUtil.notEqual(coupon.getStatus(), UN_ISSUE) && ObjectUtil.notEqual(coupon.getStatus(), DRAFT)){
            throw new BadRequestException("优惠券状态不正确");
        }

        // 3.修改优惠券信息
        BeanUtil.copyProperties(couponFormDTO, coupon);
        updateById(coupon);

        // 4.修改优惠券限定范围信息
        // 4.1删除优惠券限定范围信息
        scopeService.lambdaUpdate().eq(CouponScope::getCouponId, coupon.getId()).remove();

        // 4.2判断是否有限定范围
        if (!couponFormDTO.getSpecific()){
            return;
        }

        // 4.3保存优惠券限定范围信息
        saveScope(couponFormDTO, coupon);

    }

    /**
     * 根据id查询优惠券
     *
     * @param id
     * @return
     */
    @Override
    public CouponDetailVO queryCouponById(Long id) {
        // 1.查询优惠券基本信息
        Coupon coupon = this.getById(id);
        if (ObjectUtil.isEmpty(coupon)){
            throw new BadRequestException("优惠券不存在");
        }
        CouponDetailVO vo = BeanUtil.toBean(coupon, CouponDetailVO.class);

       // 2.查询优惠券限定范围信息
        if (coupon.getSpecific()){
            // 有限定范围进行范围信息填充
            List<CouponScope> scopeList = scopeService.lambdaQuery().eq(CouponScope::getCouponId, id).list();
            if(ObjectUtil.isEmpty(scopeList)){
                log.error("优惠券限定范围信息为空，优惠券id:{}", id);
            }
            // 2.1获取限定范围id集合
            List<Long> bizIds = scopeList.stream().map(CouponScope::getBizId).collect(Collectors.toList());

            // 2.2获取分类信息集合
            Map<Long, CategoryBasicDTO> categoryMap = categoryCache.getCategoryMap();
            List<CouponScopeVO> scopeVOList = new ArrayList<>(bizIds.size());
            for (Long bizId : bizIds) {
                scopeVOList.add(new CouponScopeVO(bizId, categoryMap.get(bizId).getName()));
            }

            // 3.填充限定范围信息
            vo.setScopes(scopeVOList);
        }
        return vo;
    }

    /**
     * 删除优惠券信息
     *
     * @param id
     */
    @Override
    @Transactional
    public void deleteCouponById(Long id) {
        // 1.删除优惠券信息
        this.removeById(id);

        // 2.删除优惠券限定范围信息
        scopeService.lambdaUpdate().eq(CouponScope::getCouponId, id).remove();

    }

    /**
     * 暂停优惠券发放
     *
     * @param id
     */
    @Override
    public void pauseCoupon(Long id) {
        Coupon coupon = this.getById(id);
        if (ObjectUtil.isEmpty(coupon)){
            throw new BadRequestException("优惠券不存在");
        }
        this.updateById(coupon.setStatus(PAUSE));
    }

    /**
     * 查询发放中的优惠券列表
     *
     * @return
     */
    @Override
    public List<CouponVO> queryCouponList() {
        // 1.查询发放中的优惠券列表
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getStatus, ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if (ObjectUtil.isEmpty(couponList)){
            // 返回空集合
            log.info("发放中的优惠券列表为空");
          return Collections.emptyList();
        }

        // 2.查询当前用户已领取当前发放的优惠券信息
        Set<Long> ids = couponList.stream().map(Coupon::getId).collect(Collectors.toSet());
        List<UserCoupon> list = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, ids)
                .list();


        // 3.统计当用户对优惠券的已领取数量
        Map<Long, Long> couponCountMap = list.stream().collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));

        // 4.统计当前用户对优惠券的已领取并且未使用的数量
        Map<Long, Long> unUsedCouponMap = list.stream()
                .filter(d -> ObjectUtil.equals(d.getStatus(), UserCouponStatus.UNUSED))
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));

        List<CouponVO> vos = new ArrayList<>(couponList.size());
        // 5.遍历优惠券列表，填充优惠券信息
        for (Coupon c : couponList) {
            CouponVO vo = BeanUtil.toBean(c, CouponVO.class);

            // 判断优惠券是否发行完毕,ture 表示未发行完
            boolean unIssued = c.getTotalNum() > c.getIssueNum();

            // 判断是否可以继续领券,用户已领数量小于限制数量，且优惠券未达到发放数量
            boolean available = couponCountMap.getOrDefault(c.getId(), 0L) < c.getUserLimit() && unIssued;

           // 判断是否可以使用，有未使用的券且在有效期内
            boolean received = unUsedCouponMap.getOrDefault(c.getId(), 0L) > 0;
            vo.setAvailable(available);
            vo.setReceived(received);
            vos.add(vo);
        }

        return vos;
    }
}
